﻿/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.
This code is licensed under the Visual Studio SDK license terms.
THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/

using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using EnvDTE;
using VSLangProj;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Project.Automation;
using Microsoft.VisualStudio.Project;
using PS3Helpers;
using PS3Helpers.Helpers;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using MSBuild = Microsoft.Build.Evaluation;

namespace rfc1459.PS3Project
{
    /// <summary>
    /// This class extends the ProjectNode in order to represent our project 
    /// within the hierarchy.
    /// </summary>
    [Guid("6FC514F7-6F4D-4FD4-95ED-F37F61E798EF")]
    public class PlaystationProjectNode : ProjectNode
    {
        #region Enum for image list
        internal enum PlaystationProjectImageName
        {
            Project = 0,
        }
        #endregion

        #region Constants
        internal const string ProjectTypeName = "PlaystationProject";
        #endregion

        #region Fields
        private PlaystationProjectPackage package;
        internal static int imageOffset;
        private static ImageList imageList;
        private VSLangProj.VSProject vsProject;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes the <see cref="PlaystationProjectNode"/> class.
        /// </summary>
        static PlaystationProjectNode()
        {
            imageList = Utilities.GetImageList(typeof(PlaystationProjectNode).Assembly.GetManifestResourceStream("rfc1459.PS3Project.Resources.Playstation.png"));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PlaystationProjectNode"/> class.
        /// </summary>
        /// <param name="package">Value of the project package for initialize internal package field.</param>
        public PlaystationProjectNode(PlaystationProjectPackage package)
        {
            this.package = package;

            InitializeImageList();

            this.CanProjectDeleteItems = true;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the image list.
        /// </summary>
        /// <value>The image list.</value>
        public static ImageList ImageList
        {
            get
            {
                return imageList;
            }
            set
            {
                imageList = value;
            }
        }

        protected internal VSLangProj.VSProject VSProject
        {
            get
            {
                if (vsProject == null)
                {
                    vsProject = new OAVSProject(this);
                }

                return vsProject;
            }
        }
        #endregion

        #region Fix bug in base ProjectNode

        protected internal override void ProcessFiles()
        {
            List<String> subitemsKeys = new List<String>();
            Dictionary<String, MSBuild.ProjectItem> subitems = new Dictionary<String, MSBuild.ProjectItem>();

            // Define a set for our build items. The value does not really matter here.
            Dictionary<String, MSBuild.ProjectItem> items = new Dictionary<String, MSBuild.ProjectItem>();

            // Process Files
            // Create a copy of the list of all the projectitems. We do this because the list will be modified if there is subfolders in the template.
            List<MSBuild.ProjectItem> projectitems = new List<MSBuild.ProjectItem>(this.BuildProject.Items);
            foreach (MSBuild.ProjectItem item in projectitems)
            {
                // Ignore the item if it is a reference or folder
                if (this.FilterItemTypeToBeAddedToHierarchy(item.ItemType))
                    continue;

                // MSBuilds tasks/targets can create items (such as object files),
                // such items are not part of the project per say, and should not be displayed.
                // so ignore those items.
                if (!this.IsItemTypeFileType(item.ItemType))
                    continue;

                // If the item is already contained do nothing.
                // TODO: possibly report in the error list that the the item is already contained in the project file similar to Language projects.
                if (items.ContainsKey(item.EvaluatedInclude.ToUpperInvariant()))
                    continue;

                // Make sure that we do not want to add the item, dependent, or independent twice to the ui hierarchy
                items.Add(item.EvaluatedInclude.ToUpperInvariant(), item);

                string dependentOf = item.GetMetadataValue(ProjectFileConstants.DependentUpon);

                if (!this.CanFileNodesHaveChilds || String.IsNullOrEmpty(dependentOf))
                {
                    AddIndependentFileNode(item);
                }
                else
                {
                    // We will process dependent items later.
                    // Note that we use 2 lists as we want to remove elements from
                    // the collection as we loop through it
                    subitemsKeys.Add(item.EvaluatedInclude);
                    subitems.Add(item.EvaluatedInclude, item);
                }
            }

            // Now process the dependent items.
            if (this.CanFileNodesHaveChilds)
            {
                ProcessDependentFileNodes(subitemsKeys, subitems);
            }

        }

        private HierarchyNode AddIndependentFileNode(MSBuild.ProjectItem item)
        {
            HierarchyNode currentParent = GetItemParentNode(item);
            return AddFileNodeToNode(item, currentParent);
        }

        private HierarchyNode AddFileNodeToNode(MSBuild.ProjectItem item, HierarchyNode parentNode)
        {
            FileNode node = this.CreateFileNode(new ProjectElement(this, item, false));
            parentNode.AddChild(node);
            return node;
        }

        private HierarchyNode GetItemParentNode(MSBuild.ProjectItem item)
        {
            HierarchyNode currentParent = this;
            string strPath = item.EvaluatedInclude;

            strPath = Path.GetDirectoryName(strPath);
            if (strPath.Length > 0)
            {
                // Use the relative to verify the folders...
                currentParent = this.CreateFolderNodes(strPath);
            }
            return currentParent;
        }

        #endregion Fix bug in base ProjectNode

        #region Overriden implementation

        /// <summary>
        /// Gets the project GUID.
        /// </summary>
        /// <value>The project GUID.</value>
        public override Guid ProjectGuid
        {
            get { return typeof(PlaystationProjectFactory).GUID; }
        }

        /// <summary>
        /// Gets the type of the project.
        /// </summary>
        /// <value>The type of the project.</value>
        public override string ProjectType
        {
            get { return ProjectTypeName; }
        }

        /// <summary>
        /// Return an imageindex
        /// </summary>
        /// <value></value>
        /// <returns></returns>
        public override int ImageIndex
        {
            get
            {
                return imageOffset + (int)PlaystationProjectImageName.Project;
            }
        }

        /// <summary>
        /// Returns an automation object representing this node
        /// </summary>
        /// <returns>The automation object</returns>
        public override object GetAutomationObject()
        {
            return new OAPlaystationProject(this);
        }

        /// <summary>
        /// Creates the file node.
        /// </summary>
        /// <param name="item">The project element item.</param>
        /// <returns></returns>
        public override FileNode CreateFileNode(ProjectElement item)
        {
            PlaystationFileNode node = new PlaystationFileNode(this, item);

            node.OleServiceProvider.AddService(typeof(EnvDTE.Project), new OleServiceProvider.ServiceCreatorCallback(this.CreateServices), false);
            node.OleServiceProvider.AddService(typeof(ProjectItem), node.ServiceCreator, false);
            node.OleServiceProvider.AddService(typeof(VSProject), new OleServiceProvider.ServiceCreatorCallback(this.CreateServices), false);

            return node;
        }

        /// <summary>
        /// Generate new Guid value and update it with GeneralPropertyPage GUID.
        /// </summary>
        /// <returns>Returns the property pages that are independent of configuration.</returns>
        protected override Guid[] GetConfigurationIndependentPropertyPages()
        {
            Guid[] result = new Guid[1];
            result[0] = typeof(GeneralPropertyPage).GUID;
            return result;
        }

        /// <summary>
        /// Overriding to provide project general property page.
        /// </summary>
        /// <returns>Returns the GeneralPropertyPage GUID value.</returns>
        protected override Guid[] GetPriorityProjectDesignerPages()
        {
            Guid[] result = new Guid[1];
            result[0] = typeof(GeneralPropertyPage).GUID;
            return result;
        }

        /// <summary>
        /// Adds the file from template.
        /// </summary>
        /// <param name="source">The source template.</param>
        /// <param name="target">The target file.</param>
        public override void AddFileFromTemplate(string source, string target)
        {
            if (!File.Exists(source))
            {
                throw new FileNotFoundException(string.Format("Template file not found: {0}", source));
            }

            // The class name is based on the new file name
            string className = Path.GetFileNameWithoutExtension(target);
            string namespce = this.FileTemplateProcessor.GetFileNamespace(target, this);

            this.FileTemplateProcessor.AddReplace("%className%", className);
            this.FileTemplateProcessor.AddReplace("%namespace%", namespce);
            try
            {
                this.FileTemplateProcessor.UntokenFile(source, target);

                this.FileTemplateProcessor.Reset();
            }
            catch (Exception e)
            {
                throw new FileLoadException("Failed to add template file to project", target, e);
            }
        }

        protected override ConfigProvider CreateConfigProvider()
        {
            return new PlaystationConfigProvider(this);
        }
        #endregion

        #region Private implementation
        private void InitializeImageList()
        {
            imageOffset = this.ImageHandler.ImageList.Images.Count;

            foreach (Image img in ImageList.Images)
            {
                this.ImageHandler.AddImage(img);
            }
        }

        private object CreateServices(Type serviceType)
        {
            object service = null;
            if (typeof(VSLangProj.VSProject) == serviceType)
            {
                service = this.VSProject;
            }
            else if (typeof(EnvDTE.Project) == serviceType)
            {
                service = this.GetAutomationObject();
            }
            return service;
        }
        #endregion

        #region Overriden build functionality

        protected override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
            if (cmdGroup == VsMenus.guidStandardCommandSet97)
            {
                switch ((VSConstants.VSStd97CmdID)cmd)
                {
                    case VSConstants.VSStd97CmdID.RebuildSln:
                        {
                            return VSConstants.S_OK;
                        }
                    case VSConstants.VSStd97CmdID.BuildSln:
                        {
                            return VSConstants.S_OK;
                        }
                }
            }
            /*
            else if (cmdGroup == VsMenus.guidStandardCommandSet2K)
            {
                switch ((VSConstants.VSStd2KCmdID)cmd)
                {
                    case VSConstants.VSStd2KCmdID.ADDREFERENCE:
                        return this.AddProjectReference();

                    case VSConstants.VSStd2KCmdID.ADDWEBREFERENCE:
                    case VSConstants.VSStd2KCmdID.ADDWEBREFERENCECTX:
                        return this.AddWebReference();

                    //case ExploreFolderInWindowsCommand: // constant = 1635
                    //    string explorerPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe");
                    //    System.Diagnostics.Process.Start(explorerPath, this.ProjectFolder);
                    //    return VSConstants.S_OK;
                }
            }
            */

            return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut);
        }

        protected override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result)
        {
            if (cmdGroup == VsMenus.guidStandardCommandSet97)
            {
                switch ((VSConstants.VSStd97CmdID)cmd)
                {
                    case VSConstants.VSStd97CmdID.Copy:
                    case VSConstants.VSStd97CmdID.Paste:
                    case VSConstants.VSStd97CmdID.Cut:
                    case VSConstants.VSStd97CmdID.Rename:
                    case VSConstants.VSStd97CmdID.Exit:
                    case VSConstants.VSStd97CmdID.ProjectSettings:
                    case VSConstants.VSStd97CmdID.BuildSln:
                    case VSConstants.VSStd97CmdID.UnloadProject:
                        result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
                        return VSConstants.S_OK;

                    case VSConstants.VSStd97CmdID.ViewForm:
                        //if (this.HasDesigner)
                        //{
                        //    result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
                        //    return VSConstants.S_OK;
                        //}
                        //break;
                        result |= QueryStatusResult.INVISIBLE;
                        return VSConstants.S_OK;

                    case VSConstants.VSStd97CmdID.CancelBuild:
                        result |= QueryStatusResult.SUPPORTED;
                        //if (this.buildInProcess)
                        //    result |= QueryStatusResult.ENABLED;
                        //else
                        //    result |= QueryStatusResult.INVISIBLE;
                        result |= QueryStatusResult.INVISIBLE;
                        return VSConstants.S_OK;

                    case VSConstants.VSStd97CmdID.NewFolder:
                    case VSConstants.VSStd97CmdID.AddNewItem:
                    case VSConstants.VSStd97CmdID.AddExistingItem:
                        result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
                        return VSConstants.S_OK;

                    case VSConstants.VSStd97CmdID.SetStartupProject:
                        result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
                        return VSConstants.S_OK;
                }
            }
            else if (cmdGroup == VsMenus.guidStandardCommandSet2K)
            {

                switch ((VSConstants.VSStd2KCmdID)cmd)
                {
                    case VSConstants.VSStd2KCmdID.ADDREFERENCE:
                        result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
                        return VSConstants.S_OK;

                    case VSConstants.VSStd2KCmdID.EXCLUDEFROMPROJECT:
                        result |= QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE;
                        return VSConstants.S_OK;

                    //case ExploreFolderInWindowsCommand:
                    //    result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED;
                    //    return VSConstants.S_OK;
                }
            }
            //else
            //{
            //    return (int)OleConstants.OLECMDERR_E_UNKNOWNGROUP;
            //}

            return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result);
        }

        protected override int CleanProject()
        {
            // Make sure we call the Property getter to get the VSProject!
            string projectfilename = this.VSProject.Project.FullName;
            string projectname = Path.GetFileNameWithoutExtension(projectfilename);
            string bindir = Path.GetDirectoryName(projectfilename) + Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar + "Debug" + Path.DirectorySeparatorChar;

            File.Delete(bindir + projectname + ".elf");
            File.Delete(bindir + projectname + ".self");

            return VSConstants.S_OK;
        }

        internal override void BuildAsync(uint vsopts, string config, Microsoft.VisualStudio.Shell.Interop.IVsOutputWindowPane output, string target, Action<MSBuildResult, string> uiThreadCallback)
        {
            // If this method generates an exception the calling method will catch the exception and inform VS about it.

            // Get a guid to use for this builds unique filename & folder
            Guid buildguid = Guid.NewGuid();

            // Make sure we call the Property getter to get the VSProject!
            string projectfilename = this.VSProject.Project.FullName;
            string projectdir = Path.GetDirectoryName(projectfilename) + Path.DirectorySeparatorChar;
            string binarydir = projectdir + "bin" + Path.DirectorySeparatorChar + "debug" + Path.DirectorySeparatorChar;

            string projectname = Path.GetFileNameWithoutExtension(projectfilename);


            string makefilename = projectdir + buildguid.ToString() + ".Makefile";
            try
            {
                string elfFilename = binarydir + projectname + ".elf";
                string signedElfFilename = Path.ChangeExtension(elfFilename, ".self");
                string packagefilename = Path.ChangeExtension(elfFilename, ".pkg");

                if (File.Exists(elfFilename))
                    File.Delete(elfFilename);

                if (File.Exists(signedElfFilename))
                    File.Delete(signedElfFilename);

                if (File.Exists(packagefilename))
                    File.Delete(packagefilename);

                
                using (LogInterfaceHelper loginterface = new LogInterfaceHelper(output))
                {
                    loginterface.SetCurrentStatus("Finding p3p");
                    ProjectItem packagefileitem = GetProjectItem("*.p3p");
                    if (packagefileitem == null)
                        throw new Exception("No package definition file found!");

                    using (PS3PackageFile packagefile = new PS3PackageFile(packagefileitem.FileNames[1]))
                    {
                        CreateMakeFile(loginterface, makefilename, projectdir, elfFilename);

                        // this.Build(options, pane, ((options & flags) != 0) ? MsBuildTarget.Rebuild : null);
                        PS3Builder builder = new PS3Builder(loginterface, packagefile, (target == MsBuildTarget.Rebuild), makefilename);

                        
                        using (PS3PackageCreator packager = new PS3PackageCreator(loginterface, packagefile, packagefilename, builder.SignedElfFilename))
                        {
                            /*
                            if (Properties.Settings.Default.LocalPkgAutomaticallyCopy && (!string.IsNullOrEmpty(Properties.Settings.Default.LocalPkgFolder)))
                            {
                                PackageUploadThread uploadthread = new PackageUploadThread(_logInterface, packager.PackageFileResult, Properties.Settings.Default.LocalPkgFolder);
                                uploadthread.Start();
                                WaitForOtherThread(uploadthread);
                            }

                            if (Properties.Settings.Default.PS3AutomaticallyCopy && (!string.IsNullOrEmpty(Properties.Settings.Default.PS3FtpUri)))
                            {
                                Uri uri = new Uri(Properties.Settings.Default.PS3FtpUri);

                                PackageUploadThread uploadthread = new PackageUploadThread(_logInterface, packager.PackageFileResult, uri);
                                uploadthread.Start();
                                WaitForOtherThread(uploadthread);
                            }
                            */
                        }
                    }
                }
            }
            finally
            {
                File.Delete(makefilename);
            }



//        private PlaystationProjectPackage package;
  //      internal static int imageOffset;
    //    private static ImageList imageList;
      //  private VSLangProj.VSProject vsProject;

            
            uiThreadCallback(MSBuildResult.Successful, "Compilation done");
            // base.BuildAsync(vsopts, config, output, target, uiThreadCallback);
        }

        private void CreateMakeFile(LogInterfaceHelper loginterface, string makefilename, string projectdir, string elffilename)
        {
            loginterface.SetCurrentStatus("Creating temporary makefile: " + makefilename);

            if (File.Exists(makefilename))
                File.Delete(makefilename);

            using (StreamWriter sw = File.CreateText(makefilename))
            {
                sw.WriteLine("#   PLAYSTATION(R)3 makefile created by PS3 VisualStudio Extension by rfc1459");
                sw.WriteLine("#   PS3 VisualStudio Extension Copyright (C) 2010 rfc1459");
                sw.WriteLine("");
                sw.WriteLine("CELL_MK_DIR ?= $(CELL_SDK)/samples/mk");
                sw.WriteLine("include $(CELL_MK_DIR)/sdk.makedef.mk");
                sw.WriteLine("");

                bool isfirst = true;
                foreach (ProjectItem pi in this.VSProject.Project.ProjectItems)
                {
                    Property prop = GetProjectItemProperty(pi, "BuildAction");
                    if (prop == null)
                        continue;

                    if (prop.Value is BuildAction)
                    {
                        BuildAction buildaction = (BuildAction)prop.Value;
                        if (buildaction == BuildAction.Compile)
                        {
                            sw.WriteLine("PPU_SRCS {0}= {1}", (isfirst ? "" : "+"), pi.Name);
                            isfirst = false;
                        }
                    }
                }

                string outputname = elffilename;
                if (elffilename.StartsWith(projectdir, StringComparison.CurrentCultureIgnoreCase))
                {
                    // Create a relative filename to the project directory
                    outputname = elffilename.Substring(projectdir.Length);
                    outputname = outputname.Replace(Path.DirectorySeparatorChar, '/');

                    if (!outputname.StartsWith("/"))
                        outputname = "/" + outputname;
                    outputname = "." + outputname;
                }

                sw.WriteLine("PPU_TARGET = {0}", outputname); // "./bin/debug/{0}.elf", projectname
                sw.WriteLine("");
                sw.WriteLine("include $(CELL_MK_DIR)/sdk.target.mk");
            }

            /*
            #   SCE CONFIDENTIAL                                      
            #   PLAYSTATION(R)3 Programmer Tool Runtime Library 192.001
            #   Copyright (C) 2005 Sony Computer Entertainment Inc.   
            #   All Rights Reserved.                                  

            CELL_MK_DIR ?= $(CELL_SDK)/samples/mk
            include $(CELL_MK_DIR)/sdk.makedef.mk

            PPU_SRCS = ppu_thr_simple.ppu.c
            PPU_TARGET = ppu_thr_simple.ppu.elf

            include $(CELL_MK_DIR)/sdk.target.mk
            */
        }

        private ProjectItem GetProjectItem(string filemask)
        {
            return GetProjectItem(this.VSProject.Project.ProjectItems, filemask);
        }

        private ProjectItem GetProjectItem(ProjectItems itemcollection, string filemask)
        {
            foreach (ProjectItem pi in itemcollection)
            {
                if (MatchMask(pi.Name, filemask))
                {
                    return pi;
                }

                ProjectItem result = GetProjectItem(pi.ProjectItems, filemask);
                if (result != null)
                    return result;
            }

            return null;
        }

        private bool MatchMask(string sFileName, string sFileMask)
        {
            Regex mask = new Regex(sFileMask.Replace(".", "[.]").Replace("*", ".*").Replace("?", "."));
            return mask.IsMatch(sFileName);
        }

        private Property GetProjectItemProperty(ProjectItem projectitem, string propertyname)
        {
            if ((projectitem == null) || (projectitem.Properties == null))
                return null;

            foreach (Property prop in projectitem.Properties)
            {
                if ((prop == null) || (prop.Name == null))
                    continue;

                if (prop.Name.Equals(propertyname, StringComparison.CurrentCultureIgnoreCase))
                {
                    return prop;
                }
            }

            return null;
        }

        #endregion Overriden build functionality

        private class LogInterfaceHelper : OutputLogInterface, IDisposable
        {
            Microsoft.VisualStudio.Shell.Interop.IVsOutputWindowPane _vsOutput;

            internal LogInterfaceHelper(Microsoft.VisualStudio.Shell.Interop.IVsOutputWindowPane output)
            {
                _vsOutput = output;
            }

            public void SetCurrentStep(string step)
            {
                // WriteLine("--------------------------");
                WriteLine(step);
            }

            public void SetCurrentStatus(string status)
            {
                WriteLine(status);
            }

            public void SetCurrentStatus(Exception exception)
            {
                WriteLine(exception.ToString());
            }

            public void WriteDebug(string text)
            {
                WriteLine(text);
            }

            public void Dispose()
            {
                _vsOutput = null;
            }

            private void WriteLine(string str)
            {
                _vsOutput.OutputStringThreadSafe(str + "\r\n");
            }
        }
    }
}